package jp.dodododo.aop.config;

import static jp.dodododo.aop.instrument.Transformer.*;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.interceptor.ExcludeClassInterceptors;

import jp.dodododo.aop.Aspect;
import jp.dodododo.aop.Pointcut;
import jp.dodododo.aop.annotation.Enhance;

public abstract class AspectConfigVisualization {

	public static void print(String targetClassName) {
		print((ClassLoader) null, targetClassName);
	}

	public static void print(ClassLoader loader, String targetClassName) {
		print(loader, targetClassName, null);
	}

	public static void print(String targetClassName, String methodName) {
		print(null, targetClassName, methodName);
	}

	public static void print(ClassLoader loader, String targetClassName, String methodName) {
		List<AspectDef> aspectDefList = getAspectDefList(loader, targetClassName, methodName);
		prrint(aspectDefList);
	}

	private static void prrint(List<AspectDef> aspectDefList) {
		for (AspectDef aspectDef : aspectDefList) {
			AccessibleObject staticPart = aspectDef.getStaticPart();
			System.out.println(staticPart);
			List<Pointcut> pointcutList = aspectDef.getPointcutList();
			for (int i = 0; i < pointcutList.size(); i++) {
				Pointcut pointcut = pointcutList.get(i);
				System.out.print("\t");
				System.out.print("interceptor[" + i + "]");
				System.out.print("\t");
				System.out.print(pointcut.getInterceptorStatement());
				System.out.print(";");
				System.out.println();
			}
		}
	}

	public static List<AspectDef> getAspectDefList(ClassLoader loader, String targetClassName) {
		return getAspectDefList(loader, targetClassName, null);
	}

	private static boolean isDelegateMethod(Method method) {
		return method.getName().contains(PREFIX_DELEGATE_METHOD);
	}

	public static List<AspectDef> getAspectDefList(ClassLoader loader, String targetClassName, String methodName) {
		Set<String> enhancedMethodName = new HashSet<String>();
		try {
			Class<?> targetClass = Class.forName(targetClassName, true, loader);
			Method[] declaredMethods = targetClass.getDeclaredMethods();
			for (Method method : declaredMethods) {
				if (isDelegateMethod(method) == true) {
					String name = method.getName();
					enhancedMethodName.add(name.substring(PREFIX_DELEGATE_METHOD.length() + targetClassName.length() + 1, name
							.length()));
				}
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		List<AspectDef> ret = new ArrayList<AspectDef>();
		Aspect aspect = AspectConfigs.getAspectDef(loader, targetClassName);
		if (aspect == null) {
			return ret;
		}
		if (methodName != null && enhancedMethodName.contains(methodName) == false) {
			return ret;
		}
		List<Pointcut> pointcuts = aspect.getPointcuts();
		Class<?> clazz = forName(loader, targetClassName);
		List<AccessibleObject> targetStaticParts = getTargetStaticParts(clazz, methodName);
		for (AccessibleObject staticPart : targetStaticParts) {
			AspectDef aspectDef = new AspectDef(staticPart);
			if (enhancedMethodName.contains(getName(staticPart)) == false || canEnhance(staticPart) == false) {
				continue;
			}
			ret.add(aspectDef);
			for (Pointcut pointcut : pointcuts) {
				if (pointcut.isApplied(staticPart) == true) {
					aspectDef.addPointcutList(pointcut);
				}
			}
		}
		return ret;
	}

	private static String getName(AccessibleObject staticPart) {
		if (staticPart instanceof Method) {
			return ((Method) staticPart).getName();
		} else if (staticPart instanceof Constructor<?>) {
			return "this";
		} else {
			throw new IllegalArgumentException();
		}
	}

	private static boolean canEnhance(AccessibleObject staticPart) {
		Enhance enhance = staticPart.getAnnotation(Enhance.class);
		if (enhance != null) {
			return enhance.value();
		}
		ExcludeClassInterceptors exclude = staticPart.getAnnotation(ExcludeClassInterceptors.class);
		if (exclude != null) {
			return false;
		}
		Class<?> declaringClass = null;
		if (staticPart instanceof Method) {
			Method method = (Method) staticPart;
			declaringClass = method.getDeclaringClass();
		} else if (staticPart instanceof Constructor<?>) {
			Constructor<?> constructor = (Constructor<?>) staticPart;
			declaringClass = constructor.getDeclaringClass();
		} else {
			throw new IllegalArgumentException(staticPart.getClass().getName());
		}
		enhance = declaringClass.getAnnotation(Enhance.class);
		if (enhance != null) {
			return enhance.value();
		}
		exclude = declaringClass.getAnnotation(ExcludeClassInterceptors.class);
		if (exclude != null) {
			return false;
		}
		Package pack = declaringClass.getPackage();
		enhance = pack.getAnnotation(Enhance.class);
		if (enhance != null) {
			return enhance.value();
		}
		exclude = pack.getAnnotation(ExcludeClassInterceptors.class);
		if (exclude != null) {
			return false;
		}
		return true;
	}

	private static List<Method> getAllMethods(Class<?> clazz) {
		List<Method> ret = new ArrayList<Method>();
		Method[] methods = clazz.getMethods();
		for (Method method : methods) {
			if (isDelegateMethod(method) == true) {
				continue;
			}
			ret.add(method);
		}
		Method[] declaredMethods = clazz.getDeclaredMethods();
		for (Method method : declaredMethods) {
			if (ret.contains(method) == true) {
				continue;
			}
			if (isDelegateMethod(method) == true) {
				continue;
			}
			ret.add(method);
		}
		return ret;
	}

	private static List<Constructor<?>> getAllConstructors(Class<?> clazz) {
		List<Constructor<?>> ret = new ArrayList<Constructor<?>>();
		Constructor<?>[] constructors = clazz.getConstructors();
		for (Constructor<?> constructor : constructors) {
			ret.add(constructor);
		}
		Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
		for (Constructor<?> constructor : declaredConstructors) {
			if (ret.contains(constructor) == true) {
				continue;
			}
			ret.add(constructor);
		}
		return ret;
	}

	private static List<AccessibleObject> getAllStaticParts(Class<?> clazz) {
		List<AccessibleObject> ret = new ArrayList<AccessibleObject>();
		ret.addAll(getAllMethods(clazz));
		ret.addAll(getAllConstructors(clazz));
		return ret;
	}

	private static List<AccessibleObject> getTargetStaticParts(Class<?> clazz, String methodName) {
		List<AccessibleObject> allStaticParts = getAllStaticParts(clazz);
		if (methodName == null || methodName.length() == 0) {
			return allStaticParts;
		}
		List<AccessibleObject> ret = new ArrayList<AccessibleObject>();
		if ("this".equals(methodName) == true) {
			ret.addAll(getAllConstructors(clazz));
			return ret;
		}
		List<Method> allMethods = getAllMethods(clazz);
		for (Method method : allMethods) {
			if (method.getName().equals(methodName) == true) {
				ret.add(method);
			}
		}
		return ret;
	}

	private static Class<?> forName(ClassLoader loader, String targetClassName) {
		try {
			return Class.forName(targetClassName, true, loader);
		} catch (ClassNotFoundException e) {
			throw new RuntimeException(e);
		}
	}

}
